import unittest
import operator
import compass
from compass import iterable

class IteratorTester(unittest.TestCase):
    """Tests operations on the Iterator class"""
    
    #create
    def test_create_empty_countZero(self):
        """If we call create with no values, an empty IterableExtender should be created."""
        it = iterable.create()
        self.assertIsInstance(it, iterable.IterableExtender, "The iterable was not wrapped.")
        self.assertEquals(0, it.count(), "The iterable was not empty.")
        
    def test_create_singleton_countOne(self):
        """If we call create with one value, an IterableExtender with one item should be created."""
        it = iterable.create(0)
        self.assertIsInstance(it, iterable.IterableExtender, "The iterable was not wrapped.")
        self.assertListEqual([0], it.toList(), "The wrong value was in the iterable.")
        
    def test_create_multipleItems(self):
        """If we call create with multiple values, the result should contain those items."""
        it = iterable.create(*range(5))
        self.assertIsInstance(it, iterable.IterableExtender, "The iterable was not wrapped.")
        self.assertListEqual([x for x in range(5)], [x for x in it])
    
    # extend
    def test_extend(self):
        """Wraps the given iterable with an IteratorExtensions."""
        source = range(10)
        wrapped = iterable.extend(source)
        self.assertIsInstance(wrapped, iterable.IterableExtender)
        
    def test_forever_asPlaceholder(self):
        """We can use forever to make sure two iterables have the same length."""
        first = iterable.create(1, 2, 3, 4, 5)
        second = iterable.create(1, 2, 4).concatenate(iterable.forever(lambda: 0)) # buffer out with zeros
        actual = first.zip(second, lambda x,y: x + y)
        self.assertListEqual([2, 4, 7, 4, 5], actual.toList())
        
    def test_forever_count(self):
        """We can pass a generator to forever to count."""
        i = [0]
        def inc():
            result = i[0]
            i[0] += 1
            return result
        actual = iterable.forever(inc).take(5)
        self.assertListEqual([0, 1, 2, 3, 4], actual.toList())
    
    # ctor
    def test_ctor_notIterable_throwsException(self):
        """Ensures that calling the ctor with a non-iterable throws an exception"""
        self.assertRaises(TypeError, iterable.IterableExtender.__init__, 123)
        
    def test_ctor_iterable(self):
        """If we pass an iterable, the wrapper should be returned"""  
        it = iterable.IterableExtender("Hello")
        self.assertEquals(it.iterable, "Hello")
    
    # aggregate
    def test_aggregate_aggregatorNone_throwsException(self):
        """If None is passed for the aggregator, an exception should be thrown."""
        it = iterable.extend(range(10))
        aggregator = None
        seed = 0
        self.assertRaises(ValueError, it.aggregate, aggregator, seed)
        
    def test_aggregate_multiply_seedOne_returnsProduct(self):
        """If we specify multiplication and a seed of 1, the result will be the product."""
        it = iterable.extend(range(1, 6))
        actual = it.aggregate(operator.mul, 1)
        self.assertEquals(120, actual[0], "The result should have been the product.")
        self.assertEquals(5, actual[1], "The wrong number of items were counted.")
        
    # all
    def test_all_NonePredicate_throwsException(self):
        """If we pass None for the predicate, an exception should be thrown"""
        it = iterable.extend("Hello")
        self.assertRaises(ValueError, it.all, None)
        
    def test_all_badPredicate_throwsException(self):
        """If we pass a non-callable for predicate, an exception should be thrown"""
        it = iterable.extend("Hello")
        self.assertRaises(TypeError, it.all, "BadPredicate")
        
    def test_all_doesMatch_returnsTrue(self):
        """Ensure true is returned if we check if all odd numbers are odd."""
        it = iterable.extend([i for i in range(1, 2, 10)])
        self.assertTrue(it.all(lambda x: x % 2 == 1))
        
    def test_all_doesntMatch_returnsFalse(self):
        """Ensure false is returned if an item does not satisfy the predicate."""
        it = iterable.extend([0, 2, 3, 4, 6])
        self.assertFalse(it.all(lambda x: x % 2 == 0))
        
    def test_all_defaultPredicate_nonZero_returnsTrue(self):
        """If we do not provide a predicate, we use the items default boolean evaluation."""
        it = iterable.extend([1, 2, 3, 4, 5])
        self.assertTrue(it.all())
        
    def test_all_defaultPredicate_zero_returnsFalse(self):
        """If we do not provide a predicate, we use the items default boolean evaluation."""
        it = iterable.extend([1, 2, None, 4, 5])
        self.assertFalse(it.all())
        
    # any
    def test_any_empty_returnsFalse(self):
        """If the iterable is empty, false should be returned."""
        it = iterable.create()
        result = it.any()
        self.assertFalse(result)
        
    def test_any_nonempty_returnsTrue(self):
        """If the iterable is not empty, true should be returned."""
        it = iterable.create(1)
        result = it.any()
        self.assertTrue(result)
        
    # compare
    def test_compare_otherNone_throwsException(self):
        """If the other iterable is None, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = None
        self.assertRaises(ValueError, it.compare, other)
        
    def test_compare_otherNotIterable_throwsException(self):
        """If the other iterable isn't iterable, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = 123 # not iterable
        self.assertRaises(TypeError, it.compare, other)
        
    def test_compare_comparisonNone_throwsException(self):
        """If the comparison is None, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = range(5)
        comparison = None
        self.assertRaises(ValueError, it.compare, other, comparison)
        
    def test_compare_comparisonNotCallable_throwsException(self):
        """If the comparison cannot be called, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = range(5)
        comparison = "BadComparison"
        self.assertRaises(TypeError, it.compare, other, comparison)
        
    def test_compare_bothEmpty_defaultComparison_returnsZero(self):
        """If we compare two, empty iterables, the result should be zero."""
        it = iterable.create()
        other = []
        result = it.compare(other)
        self.assertEquals(0, result)
        
    def test_compare_firstEmpty_defaultComparison_returnsNegative(self):
        """If the first iterable is empty, but the second is not, a negative number should be returned."""
        it = iterable.create()
        other = [0]
        result = it.compare(other)
        self.assertTrue(result < 0)
        
    def test_compare_secondEmpty_defaultComparison_returnsPositive(self):
        """If the second iterable is empty, but the first is not, a positive number should be returned."""
        it = iterable.create(1)
        other = []
        result = it.compare(other)
        self.assertTrue(result > 0)
        
    def test_compare_lastItemDifferent_defaultComparison_returnComparisonResult(self):
        """If the last items are different, the result should be the result of comparing the last items."""
        it = iterable.create(1, 2, 3)
        other = [1, 2, 4]
        result = it.compare(other)
        self.assertTrue(result < 0)
        
    def test_compare_rangesEqual_defaultComparison_returnZero(self):
        """If the items are the same across both iterables, the result should be zero."""
        it = iterable.create(1, 2, 3)
        other = [1, 2, 3]
        result = it.compare(other)
        self.assertTrue(result == 0)
        
    def test_compare_firstSmaller_defaultComparison_returnsNegative(self):
        """If the first iterable has less items, a negative should be returned."""
        it = iterable.create(1, 2)
        other = [1, 2, 3]
        result = it.compare(other)
        self.assertTrue(result < 0)
        
    def test_compare_otherSmaller_defaultComparison_returnsPositive(self):
        """If the other iterable has less items, a positive should be returned."""
        it = iterable.create(1, 2, 3)
        other = [1, 2]
        result = it.compare(other)
        self.assertTrue(result > 0)
        
    # concatenate
    def test_concatenate_otherNone_throwsException(self):
        """If the other iterable is None, an exception should be thrown."""
        it = iterable.extend(range(0))
        other = None
        self.assertRaises(ValueError, it.concatenate, other)
        
    def test_concatenate_otherNotIterable_throwsException(self):
        """If the other is not iterable, an exception should be thrown."""
        it = iterable.extend(range(0))
        other = 123
        self.assertRaises(TypeError, it.concatenate, other)
        
    def test_concatenate_concatenates(self):
        """Concatenate should append the items in the second list to the first list."""
        it = iterable.extend(range(5))
        other = range(5, 10)
        result = it.concatenate(other)
        self.assertIsInstance(result, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([x for x in range(10)], [x for x in result], "The items were not concatenated.")
        
    # contains
    def test_contains_comparisonNone_throwsException(self):
        """If the comparison is None, an exception should be thrown."""
        it = iterable.extend(range(10))
        item = 5
        comparison = None
        self.assertRaises(ValueError, it.contains, item, comparison)
        
    def test_contains_itemExists_defaultComparison_returnsTrue(self):
        """If the item exists in the iterable, the result should be true."""
        it = iterable.extend(range(10))
        item = 5
        result = it.contains(item)
        self.assertTrue(result)
        
    def test_contains_itemMissing_defaultComparison_returnsFalse(self):
        """If the item does not exist in the iterable, the result should be false."""
        it = iterable.extend(range(10))
        item = 10
        result = it.contains(item)
        self.assertFalse(result)
    
    # count        
    def test_count(self):
        """If we ask for the count of an iterable, it should equal the number of items."""
        it = iterable.extend("Hello")
        self.assertEquals(it.count(), 5)
        it = iterable.extend((1, 2))
        self.assertEquals(it.count(), 2)
        it = iterable.extend([1, 2, "hello"])
        self.assertEquals(it.count(), 3)
        
    # distinct
    def test_distinct_keySelectorNone_throwsException(self):
        """If we pass None for a key selector, an exception should be thrown."""
        it = iterable.extend("hello")
        keySelector = None
        self.assertRaises(ValueError, it.distinct, keySelector)
        
    def test_distinct_keySelectorBad_throwsException(self):
        """If we pass a non-callable key selector, an exception should be thrown."""
        it = iterable.extend("hello")
        keySelector = "BadSelector"
        self.assertRaises(TypeError, it.distinct, keySelector)
    
    def test_distinct_removesDuplicates(self):
        """If we ask for the distinct items, all duplicates should be removed."""
        it = iterable.extend("This is a message")
        result = it.distinct()
        self.assertIsInstance(result, iterable.IterableExtender, "The result was not wrapped.")
        expected = ["T", "h", "i", "s", "a", "m", "e", "g", " "]
        expected.sort()
        actual = [x for x in result]
        actual.sort()
        self.assertListEqual(expected, actual, "The sets were not equal.")
        
    # equals
    def test_equals_otherNone_throwsException(self):
        """If we pass none for the other iterable, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = None
        self.assertRaises(ValueError, it.equals, other)
        
    def test_equals_otherNotIterable_throwsException(self):
        """If other is a non-iterable, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = 123
        self.assertRaises(TypeError, it.equals, other)
        
    def test_equals_comparisonNone_throwsException(self):
        """If the comparison is None, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = (0, 1, 2, 3, 4)
        comparison = None
        self.assertRaises(ValueError, it.equals, other, comparison)
        
    def test_equals_comparisonBad_throwsException(self):
        """If we pass a comparison that can't be called, an exception should be thrown."""
        it = iterable.extend(range(5))
        other = (0, 1, 2, 3, 4)
        comparison = "BadComparison"
        self.assertRaises(TypeError, it.equals, other, comparison)
        
    def test_equals_bothEmpty_returnsTrue(self):
        """If we compare two empty iterables, true should be returned."""
        it = iterable.create()
        other = ()
        result = it.equals(other)
        self.assertTrue(result)
        
    def test_equals_secondEmpty_returnsFalse(self):
        """If the second iterable is empty, false should be returned."""
        it = iterable.create(1)
        other = ()
        result = it.equals(other)
        self.assertFalse(result)
        
    def test_equals_firstEmpty_returnsFalse(self):
        """If the first iterabler is empty, false should be returned."""
        it = iterable.create()
        other = (1,)
        result = it.equals(other)
        self.assertFalse(result)
        
    def test_equals_matchingItems_returnsTrue(self):
        """If all of the items in the iterable are the same, true should be returned."""
        it = iterable.extend(range(5))
        other = range(5)
        result = it.equals(other)
        self.assertTrue(result)
        
    def test_equals_lastItemDifferent_returnsFalse(self):
        """If all of the items are the same, except the last, false should be returned."""
        it = iterable.extend(range(5))
        other = (0, 1, 2, 3, 5)
        result = it.equals(other)
        self.assertFalse(result)
        
    def test_equals_differentLengths_returnsFalse(self):
        """If all of the items are the same, but the lengths are different, false should be returned."""
        it = iterable.extend(range(5))
        other = range(4)
        result = it.equals(other)
        self.assertFalse(result)
        
    # first
        
    def test_first_emptyIterable_throwsException(self):
        """If we try to get the first item out of an empty list, an exception should be thrown."""
        it = iterable.create()
        self.assertRaises(ValueError, it.first)
        
    def test_first_oneItem_returnsItem(self):
        """If we have an iterable with one item, it should be returned."""
        it = iterable.create(1)
        actual = it.first()
        self.assertEquals(1, actual)
        
    # firstOrDefault
        
    def test_firstOrDefault_emptyIterable_returnsDefault(self):
        """If we try to get the first item out of an empty list, the default should be returned."""
        it = iterable.create()
        actual = it.firstOrDefault(123)
        self.assertEquals(123, actual)
        
    def test_firstOrDefault_oneItem_returnsItem(self):
        """If we have an iterable with one item, it should be returned."""
        it = iterable.create(1)
        actual = it.firstOrDefault()
        self.assertEquals(1, actual)
        
    # flatten
    def test_flatten_empty_returnsEmpty(self):
        """If we try to flatten an empty iterable, an empty iterable should be returned."""
        it = iterable.create()
        result = it.flatten()
        self.assertIsInstance(result, iterable.IterableExtender)
        self.assertEqual(0, result.count())
        
    def test_flatten_nonIterableItems_throwsException(self):
        """If an item in the iterable isn't an iterable, an exception should be thrown."""
        it = iterable.create((1, 2, 3), "Hello", 123)
        result = it.flatten() # delayed execution
        self.assertIsInstance(result, iterable.IterableExtender)
        self.assertRaises(TypeError, result.count)
        
    def test_flatten_iterableItems_returnsFlattened(self):
        """If all of the items are iterable, their items should be returned as a single iterable."""
        it = iterable.create((1, 2, 3), "Hi", [4])
        result = it.flatten()
        self.assertIsInstance(result, iterable.IterableExtender)
        self.assertListEqual([1, 2, 3, 'H', 'i', 4], [x for x in result])
        
    # forEach
    def test_forEach_actionNone_throwsException(self):
        """If we do not provide an action, an exception should be thrown."""
        it = iterable.create()
        action = None
        self.assertRaises(ValueError, it.forEach, action)
        
    def test_forEach_actionNotCallable_throwsException(self):
        """If we pass a non-callable for an action, an exception should be thrown."""
        it = iterable.create()
        action = "BadAction"
        self.assertRaises(TypeError, it.forEach, action)
        
    def test_forEach_appliesActionForEachItem(self):
        """We will use a closure to show that each item had the action applied to it."""
        it = iterable.extend(range(5))
        numbers = []
        def action(x):
            numbers.append(x)
        it.forEach(action)
        self.assertListEqual([x for x in range(5)], numbers)
        
    # groupBy
    def test_groupBy_keySelectorNone_throwsException(self):
        """If we pass None for the key selector, an exception should be thrown."""
        it = iterable.extend(range(10))
        keySelector = None
        self.assertRaises(ValueError, it.groupBy, keySelector)
        
    def test_groupBy_keySelectorBad_throwsException(self):
        """If we pass a bad key selector, an exception should be thrown."""
        it = iterable.extend(range(10))
        keySelector = "BadSelector"
        self.assertRaises(TypeError, it.groupBy, keySelector)
        
    def test_groupBy_groupEvensOdds(self):
        """If we pass None for the key selector, an exception should be thrown."""
        it = iterable.extend(range(10))
        keySelector = lambda x: x % 2 == 0
        groups = it.groupBy(keySelector)
        self.assertIsInstance(groups, iterable.IterableExtender, "The result was not wrapped.")
        evensGroup, oddsGroup = iterable.extend(groups).partition(lambda group: group.key)
        
        evensGroup = evensGroup.first()
        self.assertIsInstance(evensGroup, iterable.Grouping, "The evens group was not wrapped.")
        self.assertTrue(evensGroup.all(keySelector), "Non-evens appeared in the evens group.")
        
        oddsGroup = oddsGroup.first()
        self.assertIsInstance(oddsGroup, iterable.Grouping, "The odds group was not wrapped.")
        self.assertTrue(oddsGroup.all(lambda x: x % 2 != 0), "Evens appeared in the odds group.")
        
    # last
    def test_last_emptyIterable_throwsException(self):
        """If we try to get the last item in an iterable, an exception should be thrown."""
        it = iterable.create()
        self.assertRaises(ValueError, it.last)
        
    def test_last_singleItem_returnsItem(self):
        """If we get the last item in an iterable with one item, that single item should be returned."""
        it = iterable.create(1)
        actual = it.last()
        self.assertEquals(1, actual)
        
    def test_last_multipleItems_returnsLastItem(self):
        """If we get the last item in an iterable with multiple items, the last item should be returned."""
        it = iterable.extend(range(10))
        actual = it.last()
        self.assertEquals(9, actual)
        
    # lastOrDefault
    def test_lastOrDefault_emptyIterable_returnsDefault(self):
        """If we try to get the last item in an iterable, the default should be returned."""
        it = iterable.create()
        actual = it.lastOrDefault(123)
        self.assertEquals(123, actual)
        
    def test_lastOrDefault_singleItem_returnsItem(self):
        """If we get the last item in an iterable with one item, that single item should be returned."""
        it = iterable.create(1)
        actual = it.lastOrDefault()
        self.assertEquals(1, actual)
        
    def test_lastOrDefault_multipleItems_returnsLastItem(self):
        """If we get the last item in an iterable with multiple items, the last item should be returned."""
        it = iterable.extend(range(10))
        actual = it.lastOrDefault()
        self.assertEquals(9, actual)
        
    # map
    def test_map_mapperNone_throwsException(self):
        """If we pass None for the mapper, an exception should be thrown."""
        it = iterable.create()
        mapper = None
        self.assertRaises(ValueError, it.map, mapper)
        
    def test_map_mapperBad_throwsException(self):
        """If we pass a bad mapper, an exception should be thrown."""
        it = iterable.create()
        mapper = "BadMapper"
        self.assertRaises(TypeError, it.map, mapper)
        
    def test_map_doubleValues(self):
        """We can use map to double every number."""
        it = iterable.create(1, 2, 3, 4, 5)
        mapper = lambda x: x * x
        actual = it.map(mapper)
        self.assertListEqual([1, 4, 9, 16, 25], [x for x in actual])
        
    # max
    def test_max_comparisonNone_throwsException(self):
        """If we pass None for the comparison, an exception should be thrown."""
        it = iterable.create()
        comparison = None
        self.assertRaises(ValueError, it.max, comparison)
        
    def test_max_comparisonBad_throwsException(self):
        """If we pass a non-callable comparison, an exception should be thrown."""
        it = iterable.create()
        comparison = "BadComparison"
        self.assertRaises(TypeError, it.max, comparison)
        
    def test_max_iterableEmpty_throwsException(self):
        """If we try to get the max item in an empty iterable, an exception should be thrown."""
        it = iterable.create()
        self.assertRaises(ValueError, it.max)
        
    def test_max_lastItemMax_returnsLastItem(self):
        """If the last item in the iterable is the max, it should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.max()
        self.assertEquals(3, actual)
        
    def test_max_firstItemMax_returnsFirstItem(self):
        """If the first item in the iterable is the max, it should be returned."""
        it = iterable.create(3, 2, 1)
        actual = it.max()
        self.assertEquals(3, actual)
        
    def test_max_middleItemMax_returnsMiddleItem(self):
        """If the middle item in the iterable is the max, it should be returned."""
        it = iterable.create(1, 3, 2)
        actual = it.max()
        self.assertEquals(3, actual)
        
    # min
    def test_min_comparisonNone_throwsException(self):
        """If we pass None for the comparison, an exception should be thrown."""
        it = iterable.create()
        comparison = None
        self.assertRaises(ValueError, it.min, comparison)
        
    def test_min_comparisonBad_throwsException(self):
        """If we pass a non-callable comparison, an exception should be thrown."""
        it = iterable.create()
        comparison = "BadComparison"
        self.assertRaises(TypeError, it.min, comparison)
        
    def test_min_iterableEmpty_throwsException(self):
        """If we try to get the min item in an empty iterable, an exception should be thrown."""
        it = iterable.create()
        self.assertRaises(ValueError, it.min)
        
    def test_min_lastItemMin_returnsLastItem(self):
        """If the last item in the iterable is the min, it should be returned."""
        it = iterable.create(3, 2, 1)
        actual = it.min()
        self.assertEquals(1, actual)
        
    def test_min_firstItemMin_returnsFirstItem(self):
        """If the first item in the iterable is the min, it should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.min()
        self.assertEquals(1, actual)
        
    def test_min_middleItemMin_returnsMiddleItem(self):
        """If the middle item in the iterable is the min, it should be returned."""
        it = iterable.create(2, 1, 3)
        actual = it.min()
        self.assertEquals(1, actual)
        
    # order
    def test_order_keySelectorNone_throwsException(self):
        """If the key selector is None, an exception should be thrown."""
        it = iterable.create(5, 4, 3, 2, 1)
        keySelector = None
        reverse = False
        self.assertRaises(ValueError, it.order, keySelector, reverse)
        
    def test_order_keySelectorBad_throwsException(self):
        """If the key selector is not callable, an exception should be thrown."""
        it = iterable.create(5, 4, 3, 2, 1)
        keySelector = "BadKeySelector"
        reverse = False
        self.assertRaises(TypeError, it.order, keySelector, reverse)
        
    def test_order_comparisonBad_throwsException(self):
        """If the comparison is non-callable, an exception should be thrown."""
        it = iterable.create(5, 4, 3, 2, 1)
        keySelector = lambda x: x
        comparison = "BadComparison"
        reverse = False
        self.assertRaises(TypeError, it.order, keySelector, comparison, reverse)
        
    def test_order_reverseNonBoolean_throwsException(self):
        """If the reverse flag is not boolean, an exception should be thrown."""
        it = iterable.create(5, 4, 3, 2, 1)
        keySelector = lambda x: x
        comparison = compass.cmp
        reverse = "BadReverse"
        self.assertRaises(TypeError, it.order, keySelector, comparison, reverse)
        
    def test_order_noArgs_sortsAscending(self):
        """If we do not supply arguments, the values should be sorted in ascending order."""
        it = iterable.create(5, 4, 3, 2, 1)
        actual = it.order().toList()
        self.assertListEqual([1, 2, 3, 4, 5], actual)
        
    def test_order_reversed_sortsDescending(self):
        """If specify to sort in reverse the value should be sorted in descending order."""
        it = iterable.create(1, 2, 3, 4, 5)
        actual = it.order(reverse=True).toList()
        self.assertListEqual([5, 4, 3, 2, 1], actual)
        
    def test_order_keySelectorLowerCase(self):
        """If specify to sort by lower case, it should be applied."""
        it = iterable.extend("aceBDF")
        actual = it.order(keySelector=str.lower).toList()
        self.assertListEqual(['a', 'B', 'c', 'D', 'e', 'F'], actual)
        
    # partition
    def test_partition_predicateNone_throwsException(self):
        """If we pass None as the predicate, an exception should be thrown."""
        it = iterable.extend(range(10))
        predicate = None
        self.assertRaises(ValueError, it.partition, predicate)
        
    def test_partition_predicateBad_throwsException(self):
        """If we pass a non-callable as the predicate, an exception should be thrown."""
        it = iterable.extend(range(10))
        predicate = "BadPredicate"
        self.assertRaises(TypeError, it.partition, predicate)
        
    def test_partition_splits(self):
        """If we pass a non-callable as the predicate, an exception should be thrown."""
        it = iterable.extend(range(10))
        evens, odds = it.partition(lambda x: x % 2 == 0)
        self.assertIsInstance(evens, iterable.IterableExtender, "The evens iterable was not wrapped.")
        self.assertTrue(evens.all(lambda x: x % 2 == 0), "There were odds in the evens iterable.")
        self.assertIsInstance(odds, iterable.IterableExtender, "The odds iterable was not wrapped.")
        self.assertTrue(odds.all(lambda x: x % 2 == 1), "There were evens in the odds iterable.")
        
    # randomSample
    def test_randomSamples_sampleCountBad_throwsException(self):
        """If the sampleCount isn't an integer, an exception should be thrown."""
        it = iterable.extend(range(5))
        sampleCount = "BadSampleCount"
        self.assertRaises(TypeError, it.randomSamples, sampleCount)
        
    def test_randomSamples_sampleCountNegative_throwsException(self):
        """If the sampleCount is negative, an exception should be thrown."""
        it = iterable.extend(range(5))
        sampleCount = -1
        self.assertRaises(ValueError, it.randomSamples, sampleCount)
        
    def test_randomSamples_generatorNone_throwsException(self):
        """If the generator is None, an exception should be thrown."""
        it = iterable.extend(range(5))
        sampleCount = -1
        generator = None
        self.assertRaises(ValueError, it.randomSamples, sampleCount, generator)
        
    def test_randomSamples_generatorBad_throwsException(self):
        """If the generator is not callable, an exception should be thrown."""
        it = iterable.extend(range(5))
        sampleCount = -1
        generator = "BadGenerator"
        self.assertRaises(ValueError, it.randomSamples, sampleCount, generator)
        
    def test_randomSamples_sampleCountZero_returnEmpty(self):
        """If the sampleCount is zero, no items should be returned."""
        it = iterable.extend(range(5))
        sampleCount = 0
        actual = it.randomSamples(sampleCount)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The wrong number of items were returned.")
        
    def test_randomSamples_sampleCountEqualsLength_returnsAll(self):
        """If the sampleCount is the same as the length of the iterable, all items should be returned."""
        it = iterable.extend(range(5))
        sampleCount = 5
        actual = it.randomSamples(sampleCount)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        expected = [0, 1, 2, 3, 4]
        self.assertListEqual(expected, actual.toList(), "Not all of the items were selected.")
        
    def test_randomSamples_sampleCountGreaterThanLength_returnsAll(self):
        """If the sampleCount is greater than the length of the iterable, all items should be returned."""
        it = iterable.extend(range(5))
        sampleCount = 10
        actual = it.randomSamples(sampleCount)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        expected = [0, 1, 2, 3, 4]
        self.assertListEqual(expected, actual.toList(), "Not all of the items were selected.")
        
    def test_randomSamples_sampleCountLessThanLength_returnsSubSet(self):
        """If the sampleCount is less than the length of the iterable, only some items should be returned."""
        it = iterable.extend(range(1, 6))
        sampleCount = 3
        actual = it.randomSamples(sampleCount)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(sampleCount, actual.count(), "The wrong number of items were returned.")
        self.assertEquals(actual.distinct().count(), actual.count(), "An item was duplicated.")
        self.assertTrue(set([1, 2, 3, 4, 5]).issuperset(set(x for x in actual)), "There were items not from the original list.")
        
    # reverse
    def test_reverse_reversesItems(self):
        """If we call reverse, it should reverse the items."""
        it = iterable.create(1, 2, 3)
        actual = it.reverse()
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([3, 2, 1], actual.toList(), "The items were not reversed.")
        
    def test_reverse_trueIterable_reversesItems(self):
        """If we call reverse on a true iterable (not a Sequence), it should still reverse the items."""
        i = [0]
        def inc():
            result = i[0]
            i[0] += 1
            return result
        
        it = iterable.forever(inc).take(3)
        actual = it.reverse()
        self.assertListEqual([2, 1, 0], actual.toList())
        
    # rotateLeft
    def test_rotateLeft_shiftBad_throwsException(self):
        """If the shift is not an integer, an exception should be thrown."""
        it = iterable.create(1, 2, 3)
        shift = "BadShift"
        self.assertRaises(TypeError, it.rotateLeft, shift)
        
    def test_rotateLeft_shiftZero_returnsSelf(self):
        """If the shift is zero, the extender should be returned."""
        it = iterable.create(1, 2, 3)
        shift = 0
        actual = it.rotateLeft(shift)
        self.assertIs(it, actual)
        
    def test_rotateLeft_shiftPositive_itemsRotatedLeft(self):
        """If the shift is positive, the iterable should be shifted to the left."""
        it = iterable.create(1, 2, 3)
        shift = 2
        actual = it.rotateLeft(shift)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([3, 1, 2], actual.toList(), "The items were not rotated.")
        
    def test_rotateLeft_shiftNegative_itemsRotatedRight(self):
        """If the shift is negative, the iterable should be shifted to the left."""
        it = iterable.create(1, 2, 3)
        shift = -2
        actual = it.rotateLeft(shift)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([2, 3, 1], actual.toList(), "The items were not rotated.")
        
    def test_rotateLeft_shiftLargerThanLength_itemsRotatedLeft(self):
        """If the shift is greater than the length of the iterable, it should still work."""
        it = iterable.create(1, 2, 3)
        shift = 4 # same as shifting 1
        actual = it.rotateLeft(shift)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([2, 3, 1], actual.toList(), "The items were not rotated.")
        
    def test_rotateLeft_negativeShiftLargerThanLength_itemsRotatedLeft(self):
        """If the shift is greater than the length of the iterable, it should still work."""
        it = iterable.create(1, 2, 3)
        shift = -4 # same as shifting 2
        actual = it.rotateLeft(shift)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([3, 1, 2], actual.toList(), "The items were not rotated.")
        
    # single
    def test_single_emptyIterable_throwsException(self):
        """If the iterable is empty, an exception should be thrown."""
        it = iterable.create()
        self.assertRaises(ValueError, it.single)
        
    def test_single_iterableTooBig_throwsException(self):
        """If the iterable has more the one item, an exception should be thrown."""
        it = iterable.create(1, 2)
        self.assertRaises(ValueError, it.single)
        
    def test_single_singleItem_returnsItem(self):
        """If the iterable has exactly one item, it should be returned."""
        it = iterable.create(1)
        value = it.single()
        self.assertEquals(1, value)
        
    # skip
    def test_skip_countNotInteger_throwsException(self):
        """If we try to pass a count that isn't an integer, an exception should be thrown."""
        it = iterable.create()
        count = "BadCount"
        self.assertRaises(TypeError, it.skip, count)
        
    def test_skip_countNegative_returnsAll(self):
        """If the skip count is negative, all the items should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skip(-1)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 2, 3], actual.toList(), "The wrong items were returned.")
        
    def test_skip_countTooBig_returnsNothing(self):
        """If the skip count is greater than the number of items, an empty iterable should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skip(4)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The wrong number of items were returned.")
        
    def test_skip_skipAllButLast_returnsLast(self):
        """If the skip count is one less than the size of the iterable, an iterable with the last item should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skip(2)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([3], actual.toList(), "The wrong items were returned.")
        
    # skipWhile
    def test_skipWhile_predicateNone_throwsException(self):
        """If we pass None for a predicate, an exception should be thrown."""
        it = iterable.create()
        predicate = None
        self.assertRaises(ValueError, it.skipWhile, predicate)
        
    def test_skipWhile_predicateBad_throwsException(self):
        """If the predicate is not callable, an exception should be thrown."""
        it = iterable.create()
        predicate = "BadPredicate"
        self.assertRaises(TypeError, it.skipWhile, predicate)
        
    def test_skipWhile_nonSatisfy_returnsNothing(self):
        """If none of the items satisfy the predicate, an empty iterable should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skipWhile(lambda x: x > 0) # all are positive
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The wrong number of items were returned.")
        
    def test_skipWhile_firstMatched_returnsAll(self):
        """If the first item satisfies the predicate, the entire iterable should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skipWhile(lambda x: x < 0) # none are negative
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 2, 3], actual.toList(), "The wrong items were returned.")
        
    def test_skipWhile_middleMatched_returnsRemaining(self):
        """If a middle item satisfies the predicate, the remainder of the iterable should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.skipWhile(lambda x: x % 2 == 1) # the second is even
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([2, 3], actual.toList(), "The wrong items were returned.")
        
    # take
    def test_take_countNotInteger_throwsException(self):
        """If we try to pass a count that isn't an integer, an exception should be thrown."""
        it = iterable.create()
        count = "BadCount"
        self.assertRaises(TypeError, it.take, count)
        
    def test_take_countNegative_returnsNone(self):
        """If the take count is negative, none of the items should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.take(-1)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The wrong number of items were returned.")
        
    def test_take_countTooBig_returnsAll(self):
        """If the take count is greater than the number of items, all the items should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.take(4)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 2, 3], actual.toList(), "The wrong items were returned.")
        
    def test_take_takeAllButLast_returnsAllButLast(self):
        """If the take count is one less than the size of the iterable, an iterable with all but the last item should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.take(2)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 2], actual.toList(), "The wrong items were returned.")
        
    # takeWhile
    def test_takeWhile_predicateNone_throwsException(self):
        """If we pass None for a predicate, an exception should be thrown."""
        it = iterable.create()
        predicate = None
        self.assertRaises(ValueError, it.takeWhile, predicate)
        
    def test_takeWhile_predicateBad_throwsException(self):
        """If the predicate is not callable, an exception should be thrown."""
        it = iterable.create()
        predicate = "BadPredicate"
        self.assertRaises(TypeError, it.takeWhile, predicate)
        
    def test_takeWhile_allSatisfy_returnsAll(self):
        """If all of the items satisfy the predicate, all of the items should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.takeWhile(lambda x: x > 0) # all are positive
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 2, 3], actual.toList(), "The wrong number of items were returned.")
        
    def test_takeWhile_secondNotMatch_returnsFirst(self):
        """If the first item satisfies the predicate, but the second doesn't, the first item should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.takeWhile(lambda x: x % 2 == 1) # second is even
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1], actual.toList(), "The wrong items were returned.")
        
    def test_takeWhile_firstNotMatch_returnsNothing(self):
        """If a first item doesn't satisfy the predicate, an empty iterable should be returned."""
        it = iterable.create(1, 2, 3)
        actual = it.takeWhile(lambda x: x % 2 == 0) # the first is odd
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The wrong number of items were returned.")
        
    # toDict
    def test_toDict_keySelectorNone_throwsException(self):
        """If keySelector is None, an exception should be thrown."""
        it = iterable.create()
        keySelector = None
        self.assertRaises(ValueError, it.toDict, keySelector)
        
    def test_toDict_keySelectorBad_throwsException(self):
        """If keySelector is not callable, an exception should be thrown."""
        it = iterable.create()
        keySelector = "BadKeySelector"
        self.assertRaises(TypeError, it.toDict, keySelector)
        
    def test_toDict_valueSelectorNone_throwsException(self):
        """If valueSelector is None, an exception should be thrown."""
        it = iterable.create()
        valueSelector = None
        self.assertRaises(ValueError, it.toDict, valueSelector=valueSelector)
        
    def test_toDict_valueSelectorBad_throwsException(self):
        """If valueSelector is not callable, an exception should be thrown."""
        it = iterable.create()
        valueSelector = "BadValueSelector"
        self.assertRaises(TypeError, it.toDict, valueSelector=valueSelector)
        
    def test_toDict_duplicateKeys_noException(self):
        """If there are multiple items with the same key, the last item with the key should be used."""
        it = iterable.create(1, 1, 2, 3, 4, 4)
        actual = it.toDict()
        self.assertIsInstance(actual, dict, "The result was not a dict.")
        expected = {x:x for x in range(1, 5)}
        self.assertDictEqual(expected, actual, "The wrong items were returned.")
        
    def test_toDict_createsDict(self):
        """Call toDict should convert an IterableExtender into a Dict."""
        it = iterable.create("Bob", "Mary", "Harry", "Joshua", "Raymond")
        keySelector = lambda x: len(x)
        actual = it.toDict(keySelector)
        self.assertIsInstance(actual, dict, "The result was not a dict.")
        expected = { 3:"Bob", 4:"Mary", 5:"Harry", 6:"Joshua", 7:"Raymond" }
        self.assertDictEqual(expected, actual, "The wrong items were returned.")
        
    # toFrozenSet
    def test_toFrozenSet_createsFrozenSet(self):
        """Calling toFrozenSet should convert an IterableExtender into a FrozenSet."""
        it = iterable.create(1, 1, 2, 3, 4, 5, 5)
        actual = it.toFrozenSet()
        self.assertIsInstance(actual, frozenset, "The result was not a frozen set.")
        self.assertListEqual([1, 2, 3, 4, 5], sorted([x for x in actual]), "The wrong items were returned.")
        
    # toList
    def test_toList_createsList(self):
        """Calling toList should convert an IterableExtender into a List."""
        it = iterable.create(1, 2, 3, 4, 5)
        actual = it.toList()
        self.assertIsInstance(actual, list, "The result was not a list.")
        self.assertListEqual([1, 2, 3, 4, 5], actual, "The wrong items were returned.")
        
    # toLookup
    def test_toLookup_keySelectorNone_throwsException(self):
        """If keySelector is None, an exception should be thrown."""
        it = iterable.create()
        keySelector = None
        self.assertRaises(ValueError, it.toLookup, keySelector)
        
    def test_toLookup_keySelectorBad_throwsException(self):
        """If keySelector is not callable, an exception should be thrown."""
        it = iterable.create()
        keySelector = "BadKeySelector"
        self.assertRaises(TypeError, it.toLookup, keySelector)
        
    def test_toLookup_valueSelectorNone_throwsException(self):
        """If valueSelector is None, an exception should be thrown."""
        it = iterable.create()
        valueSelector = None
        self.assertRaises(ValueError, it.toLookup, valueSelector=valueSelector)
        
    def test_toLookup_valueSelectorBad_throwsException(self):
        """If valueSelector is not callable, an exception should be thrown."""
        it = iterable.create()
        valueSelector = "BadValueSelector"
        self.assertRaises(TypeError, it.toLookup, valueSelector=valueSelector)
        
    def test_toLookup_duplicateKeys_createsTwoEntries(self):
        """If there are multiple items with the same key, both items should appear in the key's group."""
        it = iterable.create(1, 1, 2, 3, 4, 4)
        actual = it.toLookup()
        self.assertIsInstance(actual, iterable.Lookup, "The result was not a lookup.")
        onesGroup = actual[1]
        self.assertIsInstance(onesGroup, iterable.IterableExtender, "The ones group was not wrapped.")
        self.assertEquals(2, onesGroup.count(), "The wrong number of items were in the ones group.")
        twosGroup = actual[2]
        self.assertIsInstance(twosGroup, iterable.IterableExtender, "The twos group was not wrapped.")
        self.assertEquals(1, twosGroup.count(), "The wrong number of items were in the twos group.")
        threesGroup = actual[2]
        self.assertIsInstance(threesGroup, iterable.IterableExtender, "The threes group was not wrapped.")
        self.assertEquals(1, threesGroup.count(), "The wrong number of items were in the threes group.")
        foursGroup = actual[1]
        self.assertIsInstance(foursGroup, iterable.IterableExtender, "The fours group was not wrapped.")
        self.assertEquals(2, foursGroup.count(), "The wrong number of items were in the fours group.")
        
    # toSet
    def test_toSet_createsSet(self):
        """Calling toSet should convert an IterableExtender into a Set."""
        it = iterable.create(1, 1, 2, 3, 4, 5, 5)
        actual = it.toSet()
        self.assertIsInstance(actual, set, "The result was not a set.")
        self.assertListEqual([1, 2, 3, 4, 5], sorted([x for x in actual]), "The wrong items were returned.")
        
    # toTuple
    def test_toTuple_createSet(self):
        """Calling toTuple should convert an IterableExtender into a Tuple."""
        it = iterable.create(1, 2, 3, 4, 5)
        actual = it.toTuple()
        self.assertIsInstance(actual, tuple, "The result was not a tuple.")
        self.assertEquals((1, 2, 3, 4, 5), actual, "The wrong items were returned.")
    
    # where
    def test_where_NonePredicate_throwsException(self):
        """If we pass None for the predicate, an exception should be thrown"""
        it = iterable.extend("Hello")
        self.assertRaises(ValueError, it.where, None)
        
    def test_where_badPredicate_throwsException(self):
        """If we pass a non-callable for predicate, an exception should be thrown"""
        it = iterable.extend("Hello")
        self.assertRaises(TypeError, it.where, "BadPredicate")
        
    def test_where(self):
        """If we filter away even numbers, the remaining numbers should be odd."""
        it = iterable.extend([i for i in range(0, 10)])
        it = it.where(lambda x: x % 2 == 1)
        self.assertIsInstance(it, iterable.IterableExtender, "The result was not wrapped.")
        self.assertTrue(it.all(lambda x: x % 2 == 1), "Not all items were filtered.")
        
    def test_where_defaultPredicate_evalsTrue(self):
        """If we do not provide a predicate, items evaluating to True will be returned."""
        it = iterable.extend([0, 1, None, 3, False, True])
        it = it.where()
        self.assertIsInstance(it, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([1, 3, True], [x for x in it], "The wrong items weren't returned.")
        
    # zip
    def test_zip_otherNone_throwsException(self):
        """If we try to zip with None, an exception should be thrown."""
        it = iterable.extend(range(10))
        other = None
        zipper = (lambda x,y: x + y)
        self.assertRaises(ValueError, it.zip, other, zipper)
        
    def test_zip_otherNotIterable_throwsException(self):
        """If we try to zip with a non-iterable, an exception should be thrown."""
        it = iterable.extend(range(10))
        other = 123
        zipper = (lambda x,y: x + y)
        self.assertRaises(TypeError, it.zip, other, zipper)
        
    def test_zip_zipperNone_throwsException(self):
        """If we try to zip with a None zipper, an exception should be thrown."""
        it = iterable.extend(range(10))
        other = range(10)
        zipper = None
        self.assertRaises(ValueError, it.zip, other, zipper)
        
    def test_zip_zipperNotCallable_throwsException(self):
        """If we try to zip with a zipper that cannot be called, an exception should be thrown."""
        it = iterable.extend(range(10))
        other = range(10)
        zipper = "BadZipper"
        self.assertRaises(TypeError, it.zip, other, zipper)
        
    def test_zip_twoEmptyIterables_returnsEmptyIterable(self):
        """If we zip two empty iterables, the result should be an empty iterable."""
        it = iterable.create()
        other = []
        zipper = lambda x, y: x + y
        actual = it.zip(other, zipper)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertEquals(0, actual.count(), "The iterable had the wrong number of items.")
        
    def test_zip_secondIterableShorter_stopsPrematurely(self):
        """If we zip two iterables of different size, the result should only be as long as the shortest list."""
        it = iterable.create(1, 2, 3)
        other = [1, 2]
        zipper = lambda x, y: x + y
        actual = it.zip(other, zipper)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([2, 4], actual.toList(), "The iterable had the wrong items.")
        
    def test_zip_firstIterableShorter_stopsPrematurely(self):
        """If we zip two iterables of different size, the result should only be as long as the shortest list."""
        it = iterable.create(1, 2)
        other = [1, 2, 3]
        zipper = lambda x, y: x + y
        actual = it.zip(other, zipper)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([2, 4], actual.toList(), "The iterable had the wrong items.")
        
    def test_zip_iterablesSameLength(self):
        """If we zip two iterables of the same size, the result should map every pair."""
        it = iterable.create(1, 2, 3)
        other = [3, 2, 1]
        zipper = lambda x, y: x + y
        actual = it.zip(other, zipper)
        self.assertIsInstance(actual, iterable.IterableExtender, "The result was not wrapped.")
        self.assertListEqual([4, 4, 4], actual.toList(), "The iterable had the wrong items.")

if __name__ == '__main__':
    unittest.main(exit=False)
    #raw_input()